package com.opNaga.bpmn.palettes;

import y.util.D;
import y.util.YVersion;
import y.view.*;

import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class UMLClassNodeRealizer extends ShapeNodeRealizer {
    private NodeLabel aLabel; //attributeLabel
    private NodeLabel mLabel; //methodLabel
    private boolean clipContent;
    private boolean omitDetails;
    private String stereotype = "";
    private String constraint = "";

    /**
     * Instantiates a new UMLNodeRealizer.
     */
    public UMLClassNodeRealizer() {
        init();
    }

    void init() {
        setShapeType(RECT_3D);

        SmartNodeLabelModel model = new SmartNodeLabelModel();
        getLabel().setLabelModel(model);
        getLabel().setModelParameter(model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_TOP));

        getLabel().setFontSize(13);
        getLabel().setFontStyle(Font.BOLD);

        aLabel = new NodeLabel();
        aLabel.bindRealizer(this);
        aLabel.setAlignment(YLabel.ALIGN_LEFT);
        aLabel.setLabelModel(new SmartNodeLabelModel());

        mLabel = new NodeLabel();
        mLabel.bindRealizer(this);
        mLabel.setAlignment(YLabel.ALIGN_LEFT);
        mLabel.setLabelModel(new SmartNodeLabelModel());

        clipContent = true;
        omitDetails = false;
    }

    /**
     * Instantiates a new UMLNodeRealizer as a copy of a given
     * realizer.
     */
    public UMLClassNodeRealizer(NodeRealizer r) {
        super(r);
        if (r instanceof UMLClassNodeRealizer) {
            UMLClassNodeRealizer cnr = (UMLClassNodeRealizer) r;
            aLabel = (NodeLabel) cnr.aLabel.clone();
            aLabel.bindRealizer(this);
            mLabel = (NodeLabel) cnr.mLabel.clone();
            mLabel.bindRealizer(this);
            constraint = cnr.constraint;
            stereotype = cnr.stereotype;
            clipContent = cnr.clipContent;
            omitDetails = cnr.omitDetails;
        } else {
            init();
        }
    }

    /**
     * Returns a UMLNodERealizer that is a copy of the given
     * realizer.
     */
    public NodeRealizer createCopy(NodeRealizer r) {
        return new UMLClassNodeRealizer(r);
    }


    //////////////////////////////////////////////////////////////////////////////
    // SETTER & GETTER ///////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////


    /**
     * Set the class name to be displayed by this realizer.
     */
    public void setClassName(String name) {
        setLabelText(name);
    }

    /**
     * Returns the class name to be displayed by this realizer.
     */
    public String getClassName() {
        return getLabelText();
    }


    /**
     * Sets the constraint property of this realizer.
     */
    public void setConstraint(String constraint) {
        this.constraint = constraint;
    }


    /**
     * Sets the stereotype property of this realizer.
     */
    public void setStereotype(String stereotype) {
        this.stereotype = stereotype;
    }


    /**
     * Returns the constraint property of this realizer.
     */
    public String getConstraint() {
        return constraint;
    }

    /**
     * Returns the stereotype property of this realizer.
     */
    public String getStereotype() {
        return stereotype;
    }


    /**
     * Returns the node label that represents all added
     * method strings.
     */
    public NodeLabel getMethodLabel() {
        return mLabel;
    }

    /**
     * Returns the node label that represents all added
     * attribute strings.
     */
    public NodeLabel getAttributeLabel() {
        return aLabel;
    }

    /**
     * Returns whether or not the display of the labels should be
     * clipped with the bounding box of the realizer.
     */
    public boolean getClipContent() {
        return clipContent;
    }

    /**
     * Sets whether or not the display of the labels should be
     * clipped with the bounding box of the realizer.
     */
    public void setClipContent(boolean clipping) {
        clipContent = clipping;
    }


    /**
     * Set whether or not this realizer should omit details when being displayed.
     */
    public void setOmitDetails(boolean b) {
        omitDetails = b;
    }


    /**
     * Returns whether or not this realizer should omit details when being displayed.
     */
    public boolean getOmitDetails() {
        return omitDetails;
    }

    private void addToLabel(NodeLabel l, String s) {
        if (l.getText().length() > 0) {
            l.setText(l.getText() + '\n' + s);
        } else {
            l.setText(s);
        }
    }


    /**
     * Adds a class method label to this realizer.
     */
    public void addMethod(String method) {
        addToLabel(mLabel, method);
    }

    /**
     * Adds a class attribute label to this realizer.
     */
    public void addAttribute(String attr) {
        addToLabel(aLabel, attr);
    }

    /**
     * Set the size of this realizer automatically. This method will adapt the size
     * of this realizer so that the labels defined for it will fit within its
     * bounding box.
     */
    public void fitContent() {
        double height = 3.0;
        double width = getLabel().getWidth() + 10.0;

        if (stereotype.length() > 0) {
            NodeLabel l = new NodeLabel();
            l.setText("<<" + getStereotype() + ">>");
            l.setLabelModel(new SmartNodeLabelModel());
            l.bindRealizer(this);
            height += l.getHeight() + 5.0;
            width = Math.max(l.getWidth() + 10.0, width);
        }

        height += getLabel().getHeight() + 3.0;

        if (constraint.length() > 0) {
            NodeLabel l = new NodeLabel();
            l.setText("{" + getConstraint() + "}");
            l.setLabelModel(new SmartNodeLabelModel());
            height += l.getHeight() + 5.0;
            width = Math.max(l.getWidth() + 10.0, width);
        }

        if (!omitDetails && !(aLabel.getText().length() == 0 && mLabel.getText().length() == 0)) {
            height += 3.0;
            height += aLabel.getHeight() + 3.0;
            width = Math.max(aLabel.getWidth() + 10.0, width);
            height += 3.0;
            height += mLabel.getHeight() + 3.0;
            width = Math.max(mLabel.getWidth() + 10.0, width);
        }

        setSize(width, height);
    }


    //////////////////////////////////////////////////////////////////////////////
    // GRAPHICS  /////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////

    /**
     * Paint the labels associated with this realizer.
     */
    public void paintText(Graphics2D gfx) {
        Shape oldClip = null;
        if (clipContent) {
            oldClip = gfx.getClip();
            gfx.clip(getBoundingBox());//Rect((int)x,(int)y,(int)width,(int)height);
        }

        double yoff = 3.0;

        if (stereotype.length() > 0) {
            NodeLabel l = new NodeLabel();
            l.setText("<<" + getStereotype() + ">>");
            SmartNodeLabelModel labelModel = new SmartNodeLabelModel();
            l.setLabelModel(labelModel);
            // Create a specific model parameter which aligns the node's and the label's centers horizontally
            // (nodeRatioX and labelRatioX are 0) and the upper sides of node and label vertically. (nodeRatioY
            // and labelRatioY are -0.5). In y-direction an offset is added (yoff) to to move the label to a
            // position with some distance to the border of the node.
            l.setModelParameter(labelModel.createSpecificModelParameter(0, -0.5, 0, -0.5, 0, yoff, 0, -1));
            l.bindRealizer(this);
            l.paint(gfx);
            yoff += l.getHeight() + 5.0;
        }

        NodeLabel label = getLabel();
        final SmartNodeLabelModel labelModel = new SmartNodeLabelModel();
        // Create a specific model parameter which aligns the node's and the label's centers horizontally
        // (nodeRatioX and labelRatioX are 0) and the upper sides of node and label vertically. (nodeRatioY
        // and labelRatioY are -0.5). In y-direction an offset is added (yoff) to to move the label to a
        // position with some distance to the border of the node or to the previous label respectively.
        label.setModelParameter(labelModel.createSpecificModelParameter(0, -0.5, 0, -0.5, 0, yoff, 0, -1));
        label.paint(gfx);
        yoff += label.getHeight() + 3.0;

        if (constraint.length() > 0) {
            NodeLabel l = new NodeLabel();
            l.setText("{" + getConstraint() + "}");
            SmartNodeLabelModel lModel = new SmartNodeLabelModel();
            l.setLabelModel(lModel);
            // Create a specific model parameter which aligns the node's and the label's right sides horizontally
            // (nodeRatioX and labelRatioX are 0.5) and the upper sides of node and label vertically. (nodeRatioY
            // and labelRatioY are -0.5). In y-direction an offset is added (yoff) to to move the label to a
            // position with some distance to the border of the node or to the previous label respectively. In
            // x-direction the offset (-0.5) is set so keep a distance to the border of the node.
            l.setModelParameter(lModel.createSpecificModelParameter(0.5, -0.5, 0.5, -0.5, -5.0, yoff, 0, -1));
            l.bindRealizer(this);
            l.paint(gfx);
            yoff += l.getHeight() + 5.0;
        }

        if (!omitDetails && !(aLabel.getText().length() == 0 && mLabel.getText().length() == 0)) {
            gfx.setColor(getLineColor());
            gfx.drawLine((int) x + 1, (int) (y + yoff), (int) (x + width - 1.0), (int) (y + yoff));
            yoff += 3.0;
            final SmartNodeLabelModel aLabelModel = new SmartNodeLabelModel();
            // Create a specific model parameter which aligns the node's and the label's left sides horizontally
            // (nodeRatioX and labelRatioX are -0.5) and the upper sides of node and label vertically. (nodeRatioY
            // and labelRatioY are -0.5). In y-direction an offset is added (yoff) to to move the label to a
            // position with some distance to the border of the node or to the previous label respectively. In
            // x-direction the offset (3.0) is set so keep a distance to the border of the node.
            aLabel.setModelParameter(aLabelModel.createSpecificModelParameter(-0.5, -0.5, -0.5, -0.5, 3.0, yoff, 0, -1));
            aLabel.paint(gfx);
            yoff += aLabel.getHeight() + 3.0;
            gfx.drawLine((int) x + 1, (int) (y + yoff), (int) (x + width - 1.0), (int) (y + yoff));
            yoff += 3.0;
            final SmartNodeLabelModel mLabelModel = new SmartNodeLabelModel();
            mLabel.setModelParameter(mLabelModel.createSpecificModelParameter(-0.5, -0.5, -0.5, -0.5, 3.0, yoff, 0, -1));
            mLabel.paint(gfx);
        }

        if (clipContent) {
            gfx.setClip(oldClip);
        }
    }

    //////////////////////////////////////////////////////////////////////////////
    // SERIALIZATION /////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////

    /**
     * Serialization routine that allows this realizer to be written out
     * in YGF graph format.
     *
     * @deprecated Use a custom {@link y.io.graphml.graph2d.NodeRealizerSerializer}
     *             for serialization to the {@link y.io.GraphMLIOHandler GraphML format}
     *             instead.
     */
    public void write(ObjectOutputStream out) throws IOException {
        out.writeByte(YVersion.VERSION_1);
        super.write(out);
        aLabel.write(out);
        mLabel.write(out);
        out.writeBoolean(clipContent);
        out.writeBoolean(omitDetails);
        out.writeObject(getStereotype());
        out.writeObject(getConstraint());
    }


    /**
     * Deserialization routine that allows this realizer to be read in
     * from YGF graph format.
     *
     * @deprecated Use a custom {@link y.io.graphml.graph2d.NodeRealizerSerializer}
     *             for serialization to the {@link y.io.GraphMLIOHandler GraphML format}
     *             instead.
     */
    public void read(ObjectInputStream in) throws IOException, ClassNotFoundException {
        switch (in.readByte()) {
            case YVersion.VERSION_1:
                super.read(in);
                init();
                aLabel.read(in);
                mLabel.read(in);
                clipContent = in.readBoolean();
                omitDetails = in.readBoolean();
                stereotype = (String) in.readObject();
                constraint = (String) in.readObject();
                break;
            default:
                D.fatal("Unsupported Format");
        }
    }


    public static void addContentTo(final JRootPane rootPane) {
        final UMLClassNodeRealizer r = new UMLClassNodeRealizer();
        r.setClassName("com.mycompany.MyClass");
        r.setConstraint("abstract");
        r.setStereotype("factory");
        r.addAttribute("-graph");
        r.addAttribute("-id");
        r.addMethod("+setGraph(Graph)");
        r.addMethod("+getGraph():Graph");
        r.addMethod("+setID(int)");
        r.addMethod("+getID():int");
        r.fitContent();
        r.setFillColor(new Color(255, 153, 0));
        final Graph2DView view = new Graph2DView();
        view.setFitContentOnResize(true);
        view.getGraph2D().setDefaultNodeRealizer(r.createCopy());
        view.getGraph2D().createNode();
        view.addViewMode(new EditMode());

        rootPane.setContentPane(view);
    }
}